#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time      :2022/6/1 15:05
# @Author    :cjw
import os
from time import sleep
from typing import Optional, Tuple, List

from appium.webdriver import WebElement as MobileElement
from appium.webdriver.common.mobileby import AppiumBy
from appium.webdriver.webdriver import WebDriver
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from app_wework.util.appium_device_sync import AppiumDeviceSync
from app_wework.util.log import logger
from app_wework.setting import WEBDRIVER_WAIT_TIMEOUT, IMPLICITLY_WAIT_TIMEOUT


class BasePage:
	"""页面基类"""

	def __init__(self, base_driver: Optional[WebDriver] = None):
		"""
		初始化压面
		:param base_driver: 其实app对象
		"""
		self.driver = base_driver

	def find(self, loc: Tuple[str, str], is_displayed: bool = True, is_clickable: bool = False,
	         timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> MobileElement:
		"""
		查找单个元素，默认查找可见的元素
		:param loc: 定位表达式：(定位方式，定位值)
		:param is_displayed: 元素是否可见
		:param is_clickable: 元素是否可点击
		:param timeout: 元素查找，超时时间
		:return:
		"""
		# 报错信息
		msg = f'查找单个元素超时，元素定位方式：{loc[0]}, 定位内容：{loc[1]}'

		try:
			if is_displayed:
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_element_located(loc), msg)
			elif is_clickable:
				element = WebDriverWait(self.driver, timeout).until(
					EC.element_to_be_clickable(loc), msg)
			else:
				element = WebDriverWait(self.driver, timeout).until(
					lambda x: x.find_element(*loc), msg)
			return element
		except TimeoutException as ex:
			logger.error(msg)
			raise ex

	def finds(self, loc: Tuple[str, str], is_displayed: bool = True,
	          timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> List[MobileElement]:
		"""
		查找多个元素, 默认查找可见的元素
		:param loc: 定位表达式：(定位方式，定位值)
		:param is_displayed: 元素是否可见
		:param timeout: 元素查找，超时时间
		:return:
		"""
		# 报错信息
		msg = f'查找多个元素超时，元素定位方式：{loc[0]}, 定位内容：{loc[1]}'

		try:
			if is_displayed:
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_all_elements_located(loc), msg)
			else:
				element = WebDriverWait(self.driver, timeout).until(
					lambda x: x.find_elements(*loc), msg)
			return element
		except TimeoutException as ex:
			logger.error(msg)
			raise ex

	def swipe(self, direction: str = 'up', num: int = 1, is_adb: bool = False,
	          window_size: Optional[Tuple[int, int]] = None, duration: int = 5000) -> None:
		"""
		屏幕滑动
		:param direction: 滑动方向：up-上，down-下，left-左，right-右
		:param num: 滑动次数，默认1次
		:param is_adb: 是否通过adb命令滑动
		:param window_size: 屏幕尺寸: (宽度，高度)
		:param duration: 滑动持续时间，默认5000(ms)
		:return:
		"""
		# 滑动方向校验
		direction = direction.lower()
		directions = {'up': '上', 'down': '下', 'left': '左', 'right': '右'}  # 滑动方向
		if direction not in directions:  # 判断滑动方向是否存在
			logger.error(msg := f'滑动方向未定义: {direction}, 可选择的滑动方向：{directions}')
			raise ValueError(msg)

		# 判断是否需要获取屏幕大小数据
		if not window_size:
			size = self.driver.get_window_size()
			w, h = size['width'], size['height']
		else:
			w, h = window_size

		# 初始化滑动坐标
		start_x, start_y, end_x, end_y = 0, 0, 0, 0

		# 滑动
		while True:
			try:
				logger.info(f'开始向“{directions[direction]}”滑动')
				if direction == 'up':  # 上滑
					start_x, start_y = 0.5 * w, 0.8 * h
					end_x, end_y = start_x, 0.2 * h
				elif direction == 'down':  # 下滑
					start_x, start_y = 0.5 * w, 0.2 * h
					end_x, end_y = start_x, 0.8 * h
				elif direction == 'left':  # 左滑
					start_x, start_y = 0.8 * w, 0.5 * h
					end_x, end_y = 0.2 * w, start_y
				elif direction == 'right':  # 右滑
					start_x, start_y = 0.2 * w, 0.5 * h
					end_x, end_y = 0.8 * w, start_y

				# 处理后的坐标进行取整
				start_x, start_y = int(start_x), int(start_y)
				end_x, end_y = int(end_x), int(end_y)

				# 判断执行滑动方式
				if is_adb:
					# 获取当前driver的udid
					udid = self.driver.capabilities.get('udid', None)
					if udid:
						# adb 滑动命令, android版本不低于4.4，可以模拟长按事件
						command = f'adb -P 5037 -s {udid} shell input swipe {start_x} {start_y} {end_x} {end_y} {duration}'
					else:
						# 设备数量判断
						if len(devices := AppiumDeviceSync.devices()) >= 2:
							logger.error(msg := f'未指定滑动设备，且当前连接的手机设备数量大于1，不能执行adb滑动，在线设备：{devices}')
							raise ValueError(msg)
						else:
							logger.info(f'adb命令，滑动屏幕未设定udid，多设备情况下不能执行adb滑动操作')
							command = f'adb -P 5037 shell input swipe {start_x} {start_y} {end_x} {end_y} {duration}'

					# 执行adb滑动命令
					os.popen(command)
					# 等待adb滑动持续时间，避免多次滑动重合进行
					sleep(round(duration / 1000))
				else:
					self.driver.swipe(start_x, start_y, end_x, end_y, duration)
				sleep(1)  # 滑动后，间隔1秒进行下一次滑动
			except Exception as e:
				logger.error(f'向“{directions[direction]}”滑动发送异常：异常信息：{e}')
				raise e

			# 每次滑动，滑动 次数减1
			num -= 1
			if not num:
				break

	def swipe_find(self, loc: Tuple[str, str], direction: str = 'up', num: int = 3,
	               is_adb: bool = False, duration: int = 5000) -> MobileElement:
		"""
		滑动查找元素
		:param loc: 定位表达式：(定位方式，定位值)
		:param direction: 滑动方向
		:param num: 查找次数
		:param is_adb: 是否通过adb命令滑动
		:param duration:持续时间，默认5000(ms)
		:return:
		"""
		# 进入滑动查找，改变隐式等待时长，提高查找速度
		self.driver.implicitly_wait(1)

		# 获取当前屏幕大小数据
		size = self.driver.get_window_size()
		w, h = size['width'], size['height']

		for i in range(num):
			try:
				element = self.driver.find_element(*loc)
				# 找到元素后，修改隐式等待时间，然后返回找到的元素对象
				self.driver.implicitly_wait(IMPLICITLY_WAIT_TIMEOUT)
				return element
			except NoSuchElementException as e:
				logger.info(f'第{i + 1}次滑动, 未找到定位元素：{loc}')
				self.swipe(direction, is_adb=is_adb, window_size=(w, h), duration=duration)

			# 规定的滑动次数后，未找到元素，抛出异常，并修改隐式等待时间
			if i == num - 1:
				logger.error(msg := f'滑动查找{num}次，没有找到需要定位的元素：{loc}')
				self.driver.implicitly_wait(IMPLICITLY_WAIT_TIMEOUT)
				raise NoSuchElementException(msg)

	def tap(self, element: MobileElement) -> None:
		"""
		模拟手指点击，点击元素的中心点，防止点偏
		:param element: 要点击的元素对象
		:return:
		"""
		# 获取元素中心点坐标
		location = element.location  # 元素左上角坐标
		size = element.size  # 元素的尺寸，宽和高
		tap_x = location['x'] + 0.5 * size['width']
		tap_y = location['y'] + 0.5 * size['height']

		try:
			# 不传入点击的duration(持续时间)，否则会出现点不进去的情况
			self.driver.tap([(int(tap_x), int(tap_y))])
		except Exception as ex:
			logger.error(f'点击元素失败，报错信息：{ex}')
			raise ex

	def find_click(self, loc: Tuple[str, str], is_displayed: bool = True, is_clickable: bool = False,
	               timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> None:
		"""
		找到元素后点击
		:param loc: 定位表达式：（定位方式，定位内容）
		:param is_displayed: 元素是否可见
		:param is_clickable: 元素是否可点击
		:param timeout: 显示等待超时时间
		:return:
		"""
		element = self.find(loc, is_displayed, is_clickable, timeout)
		self.tap(element)

	def swipe_find_click(self, loc: Tuple[str, str], direction: str = 'up', num: int = 3,
	                     is_adb: bool = False, duration: int = 5000) -> None:
		"""
		滑动查找元素并点击
		:param loc: 定位表达式：（定位方式，定位内容）
		:param direction: 滑动方向
		:param num: 查找次数，默认3次
		:param is_adb: 否通过adb命令滑动
		:param duration: 持续时间，默认5000(ms)
		:return:
		"""
		element = self.swipe_find(loc, direction, num, is_adb, duration)
		self.tap(element)

	def send_keys(self, element: MobileElement, content: str) -> None:
		"""
		文本输入
		:param element: 要输入的元素对象
		:param content: 输入的内容
		:return:
		"""
		self.tap(element)  # 点击输入框
		element.clear()  # 清空输入框
		try:
			element.send_keys(content)  # 文本输入
		except Exception as e:
			logger.info(f'输入文本失败，输入内容：{content}, 报错信息：{e}')
			raise e

	def find_send_keys(self, loc: Tuple[str, str], content: str, is_displayed: bool = True, is_clickable: bool = False,
	                   timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> None:
		"""
		查找元素后输入内容
		:param loc: 定位表达式：（定位方式，定位内容）
		:param content: 输入的内容
		:param is_displayed: 元素是否可见
		:param is_clickable: 元素是否可点击
		:param timeout: 显示等待超时时间
		:return:
		"""
		element = self.find(loc, is_displayed, is_clickable, timeout)
		self.send_keys(element, content)

	def swipe_send_keys(self, loc: Tuple[str, str], content: str, direction: str = 'up', num: int = 5,
	                    is_adb: bool = False, duration: int = 5000) -> None:
		"""
		滑动查找元素，并输入内容
		:param loc: 定位表达式：（定位方式，定位内容）
		:param content: 输入的内容
		:param direction: 滑动方向
		:param num: 滑动次数
		:param is_adb: 是否通过adb命令滑动
		:param duration: 持续时间，默认5000(ms)
		:return:
		"""
		element = self.swipe_find(loc, direction, num, is_adb, duration)
		self.send_keys(element, content)

	@property
	def toast_text(self) -> str:
		"""获取toast系统提示信息"""
		locator = (AppiumBy.XPATH, '//*[@class="android.widget.Toast"]')
		tip_text = self.find(locator, is_displayed=False).text
		return tip_text
